package cn.sylinx.horm.spring.boot;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.sylinx.horm.config.DataSourceConfig;
import cn.sylinx.horm.config.OrmConfig;
import cn.sylinx.horm.config.OrmConfigHolder;
import cn.sylinx.horm.config.ServiceEnvironment;
import cn.sylinx.horm.config.SingleDataSourceConfig;
import cn.sylinx.horm.config.specific.SpecificConfig;
import cn.sylinx.horm.config.specific.SpecificConfigHolder;
import cn.sylinx.horm.core.DynamicClient;
import cn.sylinx.horm.core.OrmClient;
import cn.sylinx.horm.core.SqlClient;
import cn.sylinx.horm.core.TransactionSupportOrmClient;
import cn.sylinx.horm.core.datasource.NamedDataSource;
import cn.sylinx.horm.dialect.DbType;
import cn.sylinx.horm.dialect.DialectFactory;
import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.resource.parse.DefaultSqlParser;
import cn.sylinx.horm.resource.parse.SqlParser;
import cn.sylinx.horm.starter.CommonStarter.NamedDataSourcePrimary;
import cn.sylinx.horm.starter.Utils;
import cn.sylinx.horm.transaction.jdbc.JdbcTransactionalConnectionProvider;
import cn.sylinx.horm.util.GLog;

/**
 * 自行创建数据源进行初始化
 * 
 * @author johnhan
 *
 */
class SqlClientInitByNative {

    private HORMBootConfig bootConfig;

    public SqlClient create() {
        initConfig();
        initSqlClient(parseDataSource());
        return SqlClient.getDefaultClient();
    }

    private void initConfig() {
        OrmConfigHolder.init(ServiceEnvironment.SPRINGBOOT, bootConfig);
        Map<String, SpecificConfig> specificConfigMaps = new HashMap<>();
        OrmConfig ormConfig = OrmConfigHolder.getOrmConfig();
        specificConfigMaps.put(DynamicClient.DEFAULT_DS_NAME, copyFromOrmConfig(ormConfig));

        if (isMultiple()) {

            // 多数据源
            String defaultSqlClientName = null;
            List<SingleDataSourceConfig> multids = bootConfig.getDatasource().getMultids();
            if (multids == null || multids.isEmpty()) {
                throw new HORMException("请设置最少一个数据源");
            }

            for (SingleDataSourceConfig sds : multids) {

                if (sds.getPrimary() != null && sds.getPrimary()) {
                    defaultSqlClientName = sds.getName();
                }

                SpecificConfig c = copyFromOrmConfig(ormConfig);
                SpecificConfig sconfig = sds.getSpecificConfig();
                if (sconfig != null) {

                    if (sconfig.getSqlStatOpen() != null) {
                        c.setSqlStatOpen(sconfig.getSqlStatOpen());
                    }

                    if (sconfig.getSqlClientInterceptorEnable() != null) {
                        c.setSqlClientInterceptorEnable(sconfig.getSqlClientInterceptorEnable());
                    }
                }

                specificConfigMaps.put(sds.getName(), c);
            }
            if (defaultSqlClientName == null) {
                defaultSqlClientName = multids.get(0).getName();
            }

            specificConfigMaps.put(DynamicClient.DEFAULT_DS_NAME, specificConfigMaps.get(defaultSqlClientName));
        }

        SpecificConfigHolder.init(bootConfig, specificConfigMaps);
    }

    private SpecificConfig copyFromOrmConfig(OrmConfig ormConfig) {
        SpecificConfig sc = new SpecificConfig();
        sc.setSqlStatOpen(ormConfig.isSqlStatOpen());
        sc.setSqlClientInterceptorEnable(true);
        return sc;
    }

    public SqlClientInitByNative setBootConfig(HORMBootConfig bootConfig) {
        this.bootConfig = bootConfig;
        return this;
    }

    private boolean isMultiple() {
        return bootConfig.getDatasource().isMultiple();
    }

    private SqlParser getSqlParser(DbType dbTypeEnum) {
        return new DefaultSqlParser(bootConfig.getSqlPostfix(), bootConfig.isParseSqlPathDbtype() ? dbTypeEnum : null);
    }

    private List<NamedDataSource> parseDataSource() {
        List<NamedDataSource> dataSourceList = new ArrayList<>();
        if (!isMultiple()) {
            // 1.1）检查单数据源是否配置正确，并构造单数据源
            dataSourceList.add(checkAndGetSingleNamedDataSource());
        } else {
            // 1.2）检查多数据源是否配置正确，并且初始化构造多数据源
            dataSourceList.addAll(checkAndGetMultiNamedDataSource());
        }
        return dataSourceList;
    }

    private List<NamedDataSource> checkAndGetMultiNamedDataSource() {
        Set<String> nameUnique = new HashSet<>(8);
        List<NamedDataSource> dsList = new ArrayList<>();

        DataSourceConfig datasource = bootConfig.getDatasource();
        List<SingleDataSourceConfig> multids = datasource.getMultids();
        if (multids == null || multids.isEmpty()) {
            throw new HORMException("请设置最少一个数据源");
        }

        for (SingleDataSourceConfig sds : multids) {
            NamedDataSource nds = checkAndGetOneNamedDataSource(sds);
            if (nameUnique.contains(nds.getDataSourceName())) {
                throw new HORMException("datasource name should be unique");
            }
            nameUnique.add(nds.getDataSourceName());
            // 多数据源下，决定哪个是默认使用
            NamedDataSourcePrimary ndsp = toNamedDataSourcePrimary(nds, sds.getPrimary());
            dsList.add(ndsp);
        }
        return dsList;
    }

    private NamedDataSourcePrimary toNamedDataSourcePrimary(NamedDataSource nds, boolean isPrimary) {
        NamedDataSourcePrimary ndsp = new NamedDataSourcePrimary();
        ndsp.setDataSource(nds.getDataSource());
        ndsp.setDataSourceName(nds.getDataSourceName());
        ndsp.setDbType(nds.getDbType());
        ndsp.setPrimary(isPrimary);
        return ndsp;
    }

    private NamedDataSource checkAndGetSingleNamedDataSource() {
        DataSourceConfig dataSourceConfig = bootConfig.getDatasource();
        SingleDataSourceConfig singleDataSourceConfig = new SingleDataSourceConfig();
        singleDataSourceConfig.setDbtype(dataSourceConfig.getDefaultDbtype());
        singleDataSourceConfig.setDriver(dataSourceConfig.getDefaultDriver());
        singleDataSourceConfig.setName(DynamicClient.DEFAULT_DS_NAME);
        singleDataSourceConfig.setPassword(dataSourceConfig.getDefaultPassword());
        singleDataSourceConfig.setPoolConfig(dataSourceConfig.getDefaultPoolConfig());
        singleDataSourceConfig.setPooltype(dataSourceConfig.getDefaultPooltype());
        singleDataSourceConfig.setPrimary(true);
        singleDataSourceConfig.setUrl(dataSourceConfig.getDefaultUrl());
        singleDataSourceConfig.setUsername(dataSourceConfig.getDefaultUsername());
        return checkAndGetOneNamedDataSource(singleDataSourceConfig);
    }

    private NamedDataSource checkAndGetOneNamedDataSource(SingleDataSourceConfig singleDataSourceConfig) {
        return Utils.build(singleDataSourceConfig);
    }

    private void initOneSqlClient(NamedDataSource signleNamedDataSource) {
        OrmClient ormClient = new TransactionSupportOrmClient();
        ormClient.setDialect(DialectFactory.createDialect(signleNamedDataSource.getDbType()));
        ormClient.setSqlParser(getSqlParser(signleNamedDataSource.getDbType()));
        // 此处会将OrmClient句柄保存内存
        ormClient.setConnectionProvider(new JdbcTransactionalConnectionProvider(signleNamedDataSource));
        
        DynamicClient.register(ormClient);
    }

    private void initSqlClient(List<NamedDataSource> dataSourceList) {

        if (dataSourceList == null || dataSourceList.isEmpty()) {
            throw new HORMException("数据源信息丢失");
        }
        if (!isMultiple()) {
            // 单数据源
            GLog.debug("初始化单数据源 ==>> By HORM");
            initOneSqlClient(dataSourceList.get(0));

        } else {

            GLog.debug("初始化多数据源 ==>> By HORM");
            List<NamedDataSourcePrimary> transferdNdsp = new ArrayList<>();
            int primaryMult = 0;
            NamedDataSourcePrimary primaryed = null;
            // 多数据源
            for (NamedDataSource namedDataSource : dataSourceList) {
                NamedDataSourcePrimary ndsp = (NamedDataSourcePrimary) namedDataSource;
                if (ndsp.isPrimary()) {
                    primaryed = ndsp;
                    primaryMult++;
                }
                if (primaryMult > 1) {
                    throw new HORMException("primary datasource should be only one!");
                }
                transferdNdsp.add(ndsp);
            }
            NamedDataSourcePrimary primaryJumpQueue = null;
            if (primaryed == null) {
                // 都没有设置primary datasource，则取第1个
                primaryed = transferdNdsp.get(0);
            }

            primaryJumpQueue = new NamedDataSourcePrimary();
            primaryJumpQueue.setDataSource(primaryed.getDataSource());
            primaryJumpQueue.setDataSourceName(DynamicClient.DEFAULT_DS_NAME);
            primaryJumpQueue.setDbType(primaryed.getDbType());
            primaryJumpQueue.setPrimary(true);

            GLog.info("default SqlClient --> [" + primaryed.getDataSourceName() + "]");

            transferdNdsp.add(primaryJumpQueue);
            // 初始化
            transferdNdsp.forEach(this::initOneSqlClient);
        }
    }
}
